package applogic;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.StringTokenizer;


public class CalenderServer implements Receiver{
	public static CalenderServer server;
	public static Network connection;
	private Storage db;
	private HashMap<String,Login> userLoggedIn;
	private ArrayList<Message> newMessages;
	private HashMap<Integer,Room> rooms;
	private HashMap<Integer,Meeting> meetings;
	private HashMap<Integer,Appointment> appointments;
	private HashMap<String,User> users;
	private HashMap<String,HashMap<Integer,Rsvp>> rsvps;
	private int largestAid;
	
	public CalenderServer()
	{
		db = new Storage();
		userLoggedIn = new HashMap<String,Login> ();
		newMessages = new ArrayList<Message>();
		rooms = db.getAllRooms();
		meetings = db.getAllMeetings();
		users = db.getAllUsers();
		appointments = db.getAllAppointments();
		rsvps = db.getAllRsvp();
		largestAid = 0;
		for(User user: users.values())
		{
			HashMap<Integer,Rsvp> map = rsvps.get(user.getUser());
			if(map==null){
				rsvps.put(user.getUser(), new HashMap<Integer,Rsvp>());
				continue;
			}
			for(Rsvp rsvp:map.values())
			{
				Meeting meeting = meetings.get(rsvp.getAid());
				switch(rsvp.getRsvp())
				{
				case -2:
				case -1:
					meeting.setStatus("declined");
					meeting.addDecliner(user);
					break;
				case 0:
					if(meeting.getStatus().equals("all_accepted"))meeting.setStatus("waiting");
				case 1:
					meeting.addUser(user);
					break;
				case 2:
					break;
				}
			}
		}
		for(Meeting meeting: meetings.values())
		{
			if(meeting.getID()>largestAid)largestAid=meeting.getID();
			rooms.get(meeting.getRoom()).reserve(meeting);
		}
		for(Appointment appointment: appointments.values())
		{
			if(appointment.getID()>largestAid)largestAid=appointment.getID();
		}
		System.out.println(rsvps);
	}
	
	public void addMeeting(Meeting meeting){
		meeting.setID(++largestAid);
		db.saveMeeting(meeting);
		for(User user: meeting.getAttendees())
		{
			if(users.get(user.getUser())!=null)
			{
				db.saveRsvp(user,meeting);
				sendNotice(user,meeting);
			}
		}
		meetings.put(meeting.getID(),meeting);
		Message msg = new Message("store_confirmation,"+meeting.getID());
		msg.setTarget(1);
		connection.sendMessage(msg);
		if(rooms.containsKey(meeting.getRoom()))
			rooms.get(meeting.getRoom()).reserve(meeting);
	}
	
	public void editMeeting(Meeting meeting){
		db.updateMeeting(meeting);
		Meeting oldMeeting = meetings.get(meeting.getID());
		meetings.put(meeting.getID(),meeting);
		
		if(oldMeeting.getAttendees()!=null)
		{
			outer:
			for(User oUser:oldMeeting.getAttendees())
			{
				if(meeting.getAttendees()!=null)
					for(User nUser: meeting.getAttendees())
					{
						if(nUser.getUser().equals(oUser.getUser()))
						{
							continue outer;
						}
					}
				Rsvp rsvp = rsvps.get(oUser.getUser()).get(oldMeeting.getID());
				rsvp.setRsvp(2);
				db.editRsvp(rsvp);
				/* TODO: send removal notice*/
			}
		}
		
		if(meeting.getAttendees()!=null)
		{
			for(User nUser: meeting.getAttendees())
			{
				if(users.get(nUser.getUser())==null)continue;
				if(rsvps.get(nUser.getUser()).get(meeting.getID())==null)
				{
					db.saveRsvp(nUser, meeting);
					/* TODO: send meeting notice */
					//Message msg = new Message("meeting_notice,"+meeting.toMessage());
					//msg.setTarget(1);
					//connection.sendMessage(msg);
				}
				else
				{
					db.editRsvp(new Rsvp(meeting.getID(),nUser,0,""));
				}
			}
		}
	}
	
	private void sendNotice(User user, Meeting meeting)
	{
		Login match = userLoggedIn.get(user.getUser());
		if(match==null)return;
		Message message = new Message("meeting_notice,"+meeting.toMessage());
		message.setTarget(match.getIP());
	}
	
	private void addAppointment(Appointment appointment){
		appointment.setID(++largestAid);
		db.saveAppointment(appointment);
		appointments.put(appointment.getID(),appointment);
		Message msg = new Message("store_confirmation,"+appointment.getID());
		msg.setTarget(1);
		connection.sendMessage(msg);
	}
	
	private boolean login(String username, String password)
	{
		return users.containsKey(username)&&password.equals(users.get(username).getPassword());
	}
	
	private void logoff(String username)
	{
		userLoggedIn.remove(username);
	}
	
	public void receiveMessage(Message message)
	{
		newMessages.add(message);
	}
	
	public void roomRequest(String content)
	{
		StringTokenizer st = new StringTokenizer(content,",");
		int attendees = Integer.parseInt(st.nextToken());
		int month = Integer.parseInt(st.nextToken());
		int day = Integer.parseInt(st.nextToken());
		int start = Integer.parseInt(st.nextToken());
		int end = Integer.parseInt(st.nextToken());
		Calendar calendar = new GregorianCalendar();
		calendar.set(Calendar.MONTH, month);
		calendar.set(Calendar.DAY_OF_MONTH,day);
		calendar.set(Calendar.HOUR_OF_DAY, start);
		long startMillis = calendar.getTimeInMillis();
		if(end<start)calendar.set(Calendar.DAY_OF_MONTH,day+1);
		calendar.set(Calendar.HOUR_OF_DAY, end);
		long endMillis = calendar.getTimeInMillis();
		int nearest = Integer.MAX_VALUE;
		Room nearestRoom = null;
		for(Room room: rooms.values())
		{
			if(Math.abs(attendees-room.getSize())<nearest)
			{
				if(room.isAvailable(startMillis, endMillis))
				{
					nearest=room.getSize();
					nearestRoom=room;
				}
			}
		}
		Message msg = new Message("room_allocation,"+(nearestRoom==null?"0":nearestRoom.getID()));
		msg.setTarget(1);
		connection.sendMessage(msg);
	}
	
	public void parseMessage(Message message)
	{
		System.err.println("I have received the following message:\n"+message);
		String content = message.getContent();
		MsgType type = message.getType();
		switch(type)
		{
			
		case MEETING_NOTICE:
			break;
			
		case ROOM_REQUEST:{
			roomRequest(content);
			break;
		}
			
		case ROOM_ALLOCATION:
			break;
			
		case LOGIN_REQUEST:
			loginRequest(message);
			break;
			
		case LOGIN_RESULT:
			break;
			
		case MEETING_STORAGE_REQUEST:
		{
			Meeting meeting = new Meeting();
			meeting.buildFromNetMsg(content);
			if(meeting.getID()==0)addMeeting(meeting);
			else editMeeting(meeting);
			break;
		}
			
		case APPOINTMENT_STORAGE_REQUEST:
		{
			Appointment appointment = new Appointment();
			appointment.buildFromNetMsg(content);
			if(appointment.getID()==0)addAppointment(appointment);
			else db.updateAppointment(appointment);
		}
			break;
			
		case STORAGE_CONFIRMATION:
			break;
			
		case RSVP:
			rsvp(content);
			break;
			
		case CALENDER_REQUEST:
			calenderRequest(message);
			break;
			
		case CALENDER_REQUEST_RESPONSE:
			break;
			
		case CALENDER_PAYLOAD:
			break;
		case DELETE_REQUEST:
			delete(Integer.parseInt(content.substring(1)));
			break;
		case MEETING_UPDATE:
			break;
		case CANCEL_CONFIRMATION:
			cancelConfirmation(content);
			break;
			
		case DROPPED_NOTICE:
			break;
		case DROPPED_CONFIRMATION:
			confirmDrop(content);
			break;
		case DROP_USERS:
			dropUsers(content);
			break;
		}
	}
	private void dropUsers(String content) {
		StringTokenizer st = new StringTokenizer(content, ",");
		int aid = Integer.parseInt(st.nextToken());
		while(st.hasMoreElements())
		{
			db.deleteUser(users.get(st.nextToken()), meetings.get(aid));
		}
	}

	private void rsvp(String content) {
		StringTokenizer st = new StringTokenizer(content,",");
		int status = Integer.parseInt(st.nextToken());
		int aid = Integer.parseInt(st.nextToken());
		String username = st.nextToken();
		String description = st.nextToken("");
		Rsvp rsvp = rsvps.get(username).get(aid);
		Meeting meeting = meetings.get(aid);
		switch (status)
		{
		case -2:
		case -1:
			meeting.setStatus("declined");
		case 1:
		case 2:
			rsvp.setRsvp(status);
			db.editRsvp(rsvp);
			break;
		default: break;
		}
	}

	private void confirmDrop(String content) {
		StringTokenizer st = new StringTokenizer(content,",");
		String username = st.nextToken();
		int aid = Integer.parseInt(st.nextToken());
		db.deleteUser(users.get(username), meetings.get(aid));
	}

	private void cancelConfirmation(String content) {
		StringTokenizer st = new StringTokenizer(content,",");
		String username = st.nextToken();
		int aid = Integer.parseInt(st.nextToken());
		rsvps.get(username).remove(aid);
		db.deleteUser(users.get(username), meetings.get(aid));
	}

	private void delete(int ID) {
		Appointment del = appointments.get(ID);
		if(del==null)del = meetings.get(ID);
		else {deleteAppointment(del);return;}
		if(del==null)return;
		else {deleteMeeting((Meeting)del);return;}
		
	}

	private void deleteMeeting(Meeting del) {
		del.cancel();
		db.updateMeeting(del);
		//for(User user: del.getAttendees())db.deleteUser(user,del);
		Room room = rooms.get(del.getRoom());
		room.cancelRerservation(del);
	}

	private void deleteAppointment(Appointment del) {
		db.deleteAppointment(del);
		appointments.remove(del.getID());
	}

	private void calenderRequest(Message message) {
		StringTokenizer st = new StringTokenizer(message.getContent(),",");
		String user = st.nextToken();
		int week = Integer.parseInt(st.nextToken());
		ArrayList<Appointment> results = new ArrayList<Appointment>();
		Calendar calendar = new GregorianCalendar();
		System.out.println(user);
		for(Meeting meeting : meetings.values())
		{
			if(meeting.getOwner().getUser().equals(user)
					&&meeting.getCanceled()!=1)
			{
				calendar.set(Calendar.MONTH, meeting.getMonth()-1);
				calendar.set(Calendar.DAY_OF_MONTH,meeting.getDay());
				calendar.getTimeInMillis();
				if(week == calendar.get(Calendar.WEEK_OF_YEAR))
					results.add(meeting);
			}
		}
		
		for(Appointment appointment : appointments.values())
		{
			if(appointment.getOwner().getUser().equals(user))
			{
				calendar.set(Calendar.MONTH, appointment.getMonth()-1);
				calendar.set(Calendar.DAY_OF_MONTH,appointment.getDay());
				calendar.getTimeInMillis();
				if(week == calendar.get(Calendar.WEEK_OF_YEAR))
					results.add(appointment);
			}
		}
		for(Rsvp rsvp: rsvps.get(user).values())
		{
			
			Meeting meeting = meetings.get(rsvp.getAid());
			calendar.set(Calendar.MONTH, meeting.getMonth()-1);
			calendar.set(Calendar.DAY_OF_MONTH,meeting.getDay());
			calendar.getTimeInMillis();
			if(week == calendar.get(Calendar.WEEK_OF_YEAR))
				results.add(meeting);
		}
		
		Message answer = new Message("calender_request_response,"+results.size());
		answer.setTarget(1);
		connection.sendMessage(answer);
		if(results.size()==0)return;
		for(Appointment appointment:results)
		{
			answer = new Message("calender_payload"
					+(appointment instanceof Meeting?",meeting,":",appointment,")
					+appointment.toMessage());
			answer.setTarget(1);
			connection.sendMessage(answer);
		}
		
	}

	private void loginRequest(Message msg) {
		String content = msg.getContent();
		StringTokenizer st = new StringTokenizer(content,",");
		String username = st.nextToken();
		String password = st.nextToken();
		boolean login = login(username,password);
		Message answer = new Message("login_result");
		answer.setTarget(msg.getTarget());
		
		if(login)
		{
			answer.setContent(",granted");
		}
		else
		{
			answer.setContent(",wrong_password");
		}
		answer.setTarget(1);
		connection.sendMessage(answer);
		if(!login)return;
		HashMap<Integer,Rsvp> map = rsvps.get(username);
		if(map==null)return;
		for(Rsvp rsvp : map.values())
		{
			Meeting meeting = meetings.get(rsvp.getAid());
			if(meeting.getCanceled()==1
					||rsvp.getRsvp()==0)
			{
				answer = new Message("meeting_notice,"+meeting.toMessage());
				answer.setTarget(1);
				connection.sendMessage(answer);
			}
			if(rsvp.getRsvp()==2)
			{
				answer = new Message("dropped_notice,"+meeting.toMessage());
				answer.setTarget(1);
				connection.sendMessage(answer);
			}
		}
		for(Meeting meeting: meetings.values())
		{
			if(meeting.getOwner().getUser().equals(username))
			{
				if(meeting.getStatus().equals("declined")){
					answer = new Message("declined_notice,"+meeting.getDeclinerCount()+""+meeting.getDecliners()+","+meeting.toMessage());
					answer.setTarget(1);
					connection.sendMessage(answer);
				}
			}
		}
	}

	public static void initialize(Network connection)
	{
		server = new CalenderServer();
		CalenderServer.connection = connection;
	}
	
	public static void mainloop()
	{
		if(server.newMessages.size()>0)
		{
			/*
			 * Put the messages to be iterated in a holder
			 * to avoid concurrent modification should a new
			 * message be received during processing.
			 */
			ArrayList<Message> holder = server.newMessages;
			server.newMessages = new ArrayList<Message>();
			for(Message msg: holder)
			{
				server.parseMessage(msg);
			}
		}
	}
}
